package me.kevinwalker.core.handler;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.mongodb.client.FindIterable;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.model.Filters;
import me.kevinwalker.core.annotation.Collection;
import me.kevinwalker.core.annotation.MongoDB;
import me.kevinwalker.core.base.BaseMongoDB;
import me.kevinwalker.core.utils.AnnotationClassUtil;
import me.kevinwalker.core.utils.MongoDBProxyUtil;
import me.kevinwalker.core.utils.MongoDBUtil;
import org.bson.Document;
import org.bson.conversions.Bson;
import org.bson.types.ObjectId;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class BaseMongoDBInvocationHandler implements InvocationHandler {

    private Class aClass;
    private Class<?> daoClass;
    private String databaseName;
    private String collectionName;

    public BaseMongoDBInvocationHandler(Class aClass) {
        this.aClass = aClass;
        this.databaseName = ((MongoDB) aClass.getAnnotation(MongoDB.class)).value();
        this.daoClass = MongoDBProxyUtil.getInterfaceGenericClass(this.aClass, BaseMongoDB.class, 0);
        this.collectionName = daoClass.getAnnotation(Collection.class).value();
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (Object.class.equals(method.getDeclaringClass())) {
            // 已实现的具体类
            return method.invoke(this, args);
        } else {
            Object rst = null;
            // 接口
            MongoDatabase mongoDatabase = MongoDBUtil.getMongoDatabase(this.databaseName);
            MongoCollection<Document> collection = mongoDatabase.getCollection(this.collectionName);
            switch (method.getName()) {
                case "insert":
                    Document insert = insert(args);
                    collection.insertOne(insert);
                    break;
                case "insertList":
                    List<Document> documents = insertMany(args);
                    collection.insertMany(documents);
                    break;
                case "deleteById":
                    Bson eq = Filters.eq(args[0]);
                    rst = collection.deleteOne(eq).getDeletedCount();
                    break;
                case "deleteByEntity":
                    Document deleteEntity = deleteByEntity(args);
                    rst = collection.deleteOne(deleteEntity).getDeletedCount();
                    break;
                case "query":
                    if (args[0] != null) {
                        FindIterable<Document> documents1 = collection.find((Bson) args[0]);
                        ArrayList<Object> resultList = new ArrayList<>();
                        for (Document document : documents1) {
                            resultList.add(AnnotationClassUtil.documentToDao(document, this.daoClass));
                        }
                        rst = resultList;
                    } else {
                        FindIterable<Document> documents1 = collection.find();
                        ArrayList<Object> resultList = new ArrayList<>();
                        List<Document> list = new ArrayList<>();
                        for (Document document : documents1) {
                            resultList.add(AnnotationClassUtil.documentToDao(document, this.daoClass));
                        }
                        rst = resultList;
                    }
                    break;
                case "queryById":
                    Bson eq1 = Filters.eq(new ObjectId((String) args[0]));
                    FindIterable<Document> documents1 = collection.find(eq1);
                    List<Document> list = new ArrayList<>();
                    for (Document document : documents1) {
                        list.add(document);
                    }
                    if (list.size() > 0) {
                        rst = AnnotationClassUtil.documentToDao(list.get(0), this.daoClass);
                    } else {
                        throw new Exception();
                    }
                    break;
                case "createCollection":
                    mongoDatabase.createCollection((String) args[0]);
                    break;
                default:
                    Method declaredMethod = collection.getClass().getDeclaredMethod(method.getName(), method.getParameterTypes());
                    declaredMethod.setAccessible(true);
                    Object result = declaredMethod.invoke(collection, args);
                    if (result instanceof Document)
                        rst = AnnotationClassUtil.documentToDao((Document) result, this.daoClass);
                    else if (result instanceof List) {
                        ArrayList<Object> resultList = new ArrayList<>();
                        for (Object o : ((List) result)) {
                            resultList.add(AnnotationClassUtil.documentToDao((Document) o, this.daoClass));
                        }
                        rst = resultList;
                    } else
                        rst = result;

                    break;
            }
            MongoDBUtil.close();
            return rst;
        }
    }

    public Document insert(Object[] args) throws JsonProcessingException {
        ObjectMapper mapper = new ObjectMapper();
        Map<String, Object> stringObjectMap = AnnotationClassUtil.parseObjectToMap(args[0]);
        String json = mapper.writeValueAsString(stringObjectMap);
        return Document.parse(json);
    }

    public List<Document> insertMany(Object[] args) throws JsonProcessingException {
        ObjectMapper mapper = new ObjectMapper();
        ArrayList<Object> arrayList = (ArrayList<Object>) args[0];
        List<Document> list = new ArrayList<>();
        for (Object o : arrayList) {
            Map<String, Object> stringObjectMap = AnnotationClassUtil.parseObjectToMap(o);
            String json = mapper.writeValueAsString(stringObjectMap);
            Document document = Document.parse(json);
            list.add(document);
        }
        return list;
    }

    public Document deleteByEntity(Object[] args) throws JsonProcessingException {
        ObjectMapper mapper = new ObjectMapper();
        Map<String, Object> stringObjectMap = AnnotationClassUtil.parseObjectToMap(args[0]);
        String json = mapper.writeValueAsString(stringObjectMap);
        return Document.parse(json);
    }
}
